package com.quan.commons.aop;

import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import com.alibaba.fastjson.util.FieldInfo;
import com.google.common.collect.Maps;
import com.quan.commons.annotation.DeleteResource;
import com.quan.commons.annotation.GetResource;
import com.quan.commons.annotation.PostResource;
import com.quan.commons.annotation.PutResource;
import com.quan.commons.log.LogPrinter;
import com.quan.commons.utils.AopAndElUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.Iterator;
import java.util.Map;

/**
 * 接口日志AOP
 */
@Slf4j
@Aspect
@Order(650)
public class InterfaceLogAop {

    //忽略日志打印的参数类型
    private ConcurrentHashSet ignoreType = new ConcurrentHashSet();

    private LogPrinter logPrinter;

    public InterfaceLogAop(LogPrinter logPrinter) {
        this.logPrinter = logPrinter;
    }

    @PostConstruct
    public void init() {
        ignoreType.add(HttpServletResponse.class);
        ignoreType.add(HttpServletRequest.class);
        ignoreType.add(MultipartFile.class);
    }

    @Around(
            "execution(public * com.quan..*Controller.*(..)) || " +
                    "execution(public * com.quan..service..*Impl.*(..))"
    )
    public Object doInvoke(ProceedingJoinPoint point) throws Throwable {
        Object result = null;
        try {
            result = point.proceed();

            // 日志未debug模式
            if (log.isDebugEnabled()) {
            }

            return result;
        } finally {
            this.logIfsAbstract(point, result);
        }
    }

    private void logIfsAbstract(ProceedingJoinPoint point, Object result) {
        String param = null;
        if (null != point.getArgs()) {
            Map<String, Object> paramMap = AopAndElUtils.getParametersSafe(point);
            Map<String, Object> newParamMap = Maps.newHashMap();
            boolean isJsonExcetpion;
            do {
                isJsonExcetpion = false;

                for (String paramKey : paramMap.keySet()) {
                    Object paramValue = paramMap.get(paramKey);
                    if (paramValue == null) {
                        continue;
                    }
                    Class paramClazz = paramValue.getClass();
                    if (ignoreType.contains(paramClazz)) {
                        newParamMap.remove(paramKey);
                        continue;
                    }
                    newParamMap.put(paramKey, paramValue);
                }

                //try {
                //    param = JSON.toJSONString(newParamMap);
                //} catch (JSONException e) {
                //    Iterator iterator = newParamMap.values().iterator();
                //    Object paramValue = null;
                //    while (iterator.hasNext()) {
                //        paramValue = iterator.next();
                //        if (!this.isFastSerializableBean(paramValue)) {
                //            ignoreType.add(paramValue.getClass());
                //            isJsonExcetpion = true;
                //        }
                //    }
                //} catch (Exception e) {
                //    log.warn(":::打印接口入参json序列化错误:::", e);
                //}
                //
                //try {
                //    // 不打印StandardMultipartHttpServletRequest(文件上传)类型
                //    if(!(newParamMap.get("request") instanceof StandardMultipartHttpServletRequest)) {
                //        param = JSON.toJSONString(newParamMap);
                //    }
                //} catch (JSONException e) {
                //    e.printStackTrace();
                //} catch (Exception e) {
                //    log.warn(":::打印接口入参json序列化错误:::", e);
                //}
            } while (isJsonExcetpion);

        }

        final String ifsName = getInterfaceName(point);
        final String clazzName = point.getTarget().getClass().getName();
        logPrinter.info(clazzName, ifsName, param, result);
    }

    /**
     * 获取连接点上的接口名
     *
     * @param point 连接点
     * @return 接口名
     */
    private String getInterfaceName(ProceedingJoinPoint point) {
        Annotation[] annotations = AopAndElUtils.getMethodAnnotations(point);
        for (Annotation annotation : annotations) {
            if (annotation instanceof PostResource) {
                return ((PostResource) annotation).name();
            }
            if (annotation instanceof GetResource) {
                return ((GetResource) annotation).name();
            }
            if (annotation instanceof PutResource) {
                return ((PutResource) annotation).name();
            }
            if (annotation instanceof DeleteResource) {
                return ((DeleteResource) annotation).name();
            }
        }
        return point.getSignature().getName();
    }

    /**
     * 是否可以json序列化
     *
     * @param object 需序列化对象
     * @return boolean 是否允许
     */
    private boolean isFastSerializableBean(Object object) {
        Class clazz = object.getClass();
        try {
            ObjectDeserializer objectDeserializer = ParserConfig.global.getDeserializer(clazz);
            if (objectDeserializer instanceof JavaBeanDeserializer) {
                JavaBeanDeserializer javaBeanDeserializer = (JavaBeanDeserializer) objectDeserializer;
                FieldInfo[] fieldInfoArr = javaBeanDeserializer.beanInfo.fields;
                for (FieldInfo fieldInfo : fieldInfoArr) {
                    Object value = ReflectUtil.getFieldValue(object, fieldInfo.field);
                    if (value != null && !(value instanceof Serializable)) {
                        return false;
                    }
                }
            }
        } catch (Exception e) {
            return false;
        }

        return true;
    }

}
